using System;
using System.Collections.Generic;
using System.Data;
using System.Text;

namespace Developex.DatabaseWrapper.Fields
{
	/// <summary>
	/// 
	/// </summary>
	/// <typeparam name="T">Child object type</typeparam>
	public class ChildValuesField<T> : IChildValuesField where T : Row, new()
	{
		#region Internal list

		private class ChildObjectInternalList : RowList<T>
		{
			private readonly IField _keyField;
			private readonly string _foreignFieldName;

			public ChildObjectInternalList(IField keyField, string foreignFieldName)
			{
				this._keyField = keyField;
				this._foreignFieldName = foreignFieldName;
			}

			/// <summary>
			/// sender = QueryBuilder
			/// </summary>
			public event EventHandler ChangeQueryBuilder = null;

			protected override QueryBuilder GetQueryBuilder(Connection conn)
			{
				QueryBuilder qb = base.GetQueryBuilder(conn);
				qb.AddWhereCondition(conn, SampleItem.Fields[_foreignFieldName], _keyField.GetValue());
				if (ChangeQueryBuilder != null)
					ChangeQueryBuilder(qb, EventArgs.Empty);
				return qb;
			}
		}

		#endregion

		#region Public

		/// <summary>
		/// Items
		/// </summary>
		public List<T> Items = new List<T>();

		/// <summary>
		/// sender is QueryBuilder object
		/// </summary>
		public event EventHandler ChangeLoadQueryBuilder
		{
			add { ChildObjectsList.ChangeQueryBuilder += value; }
			remove { ChildObjectsList.ChangeQueryBuilder -= value; }
		}

		///<summary>
		/// Items list object used for Load operation
		///</summary>
		public RowList<T> ItemsList
		{
			get { return ChildObjectsList; }
		}

		///<summary>
		/// SampleItem for loading items list
		///</summary>
		public T SampleItem
		{
			get { return ChildObjectsList.SampleItem; }
			set { ChildObjectsList.SampleItem = value; }
		}

		#endregion

		#region Internal Poroperties

		private ChildObjectInternalList _childObjects;
		private ChildObjectInternalList ChildObjectsList
		{
			get 
			{
				return _childObjects ??
				       (_childObjects = new ChildObjectInternalList(_ownerObject.Fields[_keyFieldName], _foreignFieldName));
			}
		}

		#endregion

		#region Constructors

		private Row _ownerObject;
		private readonly string _foreignFieldName;
		private string _keyFieldName;

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="foreignFieldName"></param>
		public ChildValuesField(string foreignFieldName)
		{
			this._foreignFieldName = foreignFieldName;
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="foreignFieldName"></param>
		/// <param name="keyFieldName"></param>
		public ChildValuesField(string keyFieldName, string foreignFieldName)
		{
			this._foreignFieldName = foreignFieldName;
			this._keyFieldName = keyFieldName;
		}

		#endregion

		#region Interface implementation

		private bool _readOnly = false;

		/// <summary>
		/// Is this field read-only
		/// </summary>
		public bool ReadOnly
		{
			get { return _readOnly; }
			set { _readOnly = value; }
		}

		private bool _isInitialized = false;

		/// <summary>
		/// Is child values loaded
		/// </summary>
		public bool IsInitialized
		{
			get
			{
				return _isInitialized || Items.Count > 0;
			}
			set { _isInitialized = value; }
		}

		/// <summary>
		/// Init field
		/// </summary>
		/// <param name="owner"></param>
		public virtual void Init(Row owner)
		{
			_ownerObject = owner;
			if (string.IsNullOrEmpty(_keyFieldName))
			{
				_keyFieldName = owner.PrimaryKeyField.Name;
			}
		}

		#endregion

		#region Update/Insert/Delete/Load

		/// <summary>
		/// Fires after object loaded
		/// </summary>
		/// <param name="conn"></param>
		/// <returns></returns>
		public virtual OperationResult OnLoad(Connection conn)
		{
			_isInitialized = false;
			Items.Clear();
			if (!ChildObjectsList.Load(conn))
			{
				_ownerObject.LastException = ChildObjectsList.LastException;
				return OperationResult.ExceptionOccured;
			}
			Items.AddRange(ChildObjectsList.Items);
			_isInitialized = true;
			return OperationResult.Success;
		}

		/// <summary>
		/// Fires after object updated
		/// </summary>
		/// <param name="conn"></param>
		/// <returns></returns>
		public virtual OperationResult OnUpdate(Connection conn)
		{
			_isInitialized = false;

			IField keyField = _ownerObject.Fields[_keyFieldName];
			object keyFieldValue = keyField.GetValue();

			// update items
			foreach (T item in Items)
			{
				item.Fields[_foreignFieldName].SetValue(keyFieldValue);
				OperationResult result;
				if (item.PrimaryKeyFields.Length == 1)
				{
					result = item.Save(conn);
				}
				else
				{
					if (item.Exists(conn, item.PrimaryKeyFields))
					{
						bool doUpdate = false;
						foreach (IField assignedField in item.AssignedFields)
						{
							if ((assignedField.Flags & FieldFlags.PrimaryKey) == 0)
							{
								doUpdate = true;
								break;
							}
						}
						result = doUpdate ? item.Update(conn) : OperationResult.Success;
					}
					else
					{
						result = item.Insert(conn);
					}
				}
				if (result != OperationResult.Success)
				{
					_ownerObject.LastException = item.LastException;
					return result;
				}
			}

			// delete all other items
			QueryBuilder qb = new QueryBuilder(QueryBuilderType.Delete, SampleItem.TableName);
			qb.AddWhereCondition(conn, SampleItem.Fields[_foreignFieldName], keyFieldValue);

			if (Items.Count > 0)
			{
				IField[] pkeys = SampleItem.PrimaryKeyFields;
				if (pkeys.Length == 1)
				{
					int i = 0;
					foreach (T item in Items)
					{
						i++;
						IDataParameter par = conn.CreateParameter("par" + i, item.PrimaryKeyField.GetValue());
						qb.Parameters.Add(par);
						qb.Where.Add(string.Format("NOT {0} = {1}", item.PrimaryKeyField.Name, par.ParameterName));
					}
				}
				else
				{
					int i = 0;
					List<string> conditions = new List<string>();
					foreach (T item in Items)
					{
						List<string> conditionsNot = new List<string>();
						foreach (IField field in pkeys)
						{
							if (field.Name == _foreignFieldName) continue;
							i++;
							IDataParameter par = conn.CreateParameter("par" + i, field, item.Fields[field.Name].GetValue());
							qb.Parameters.Add(par);
							conditionsNot.Add(string.Format("{0} = {1}", field.Name, par.ParameterName));
						}
						if (conditionsNot.Count == 1)
							conditions.Add("NOT " + conditionsNot[0]);
						else
							conditions.Add("NOT(" + string.Join(" AND ", conditionsNot.ToArray()) + ")");
					}
					qb.AddWhereAndConditions(conditions);
				}
			}

			conn.ExecuteNonQuery(qb);

			_isInitialized = true;

			return OperationResult.Success;
		}

		/// <summary>
		/// Fires after object inserted
		/// </summary>
		/// <param name="conn"></param>
		/// <returns></returns>
		public virtual OperationResult OnInsert(Connection conn)
		{
			_isInitialized = false;

			IField keyField = _ownerObject.Fields[_keyFieldName];
			object keyFieldValue = keyField.GetValue();

			foreach (T item in Items)
			{
				item.Fields[_foreignFieldName].SetValue(keyFieldValue);
				OperationResult result = item.Insert(conn);
				if (result != OperationResult.Success)
				{
					_ownerObject.LastException = item.LastException;
					return result;
				}
			}

			_isInitialized = true;

			return OperationResult.Success;
		}

		/// <summary>
		/// Fires before object delete
		/// </summary>
		/// <param name="conn"></param>
		/// <returns></returns>
		public virtual OperationResult OnBeforeDelete(Connection conn)
		{
			return OperationResult.Success;
		}

		/// <summary>
		/// Fires after object delete
		/// </summary>
		/// <param name="conn"></param>
		public OperationResult OnDelete(Connection conn)
		{
			return OperationResult.Success;
		}

		#endregion
	}
}
